/*
 *  Copyright 2011 yura.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

package org.opu.vcforms.transform_t2d.util.smart_xml;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.xml.sax.Attributes;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

/**
 *
 * @author yura
 */
public class TagAccounting {
    /** list of tags that need to close after another tag */
    private Map<String, List<String>> forColsing = new HashMap<String, List<String>>();

    private Entry root;

    /**
     * Add tag at the end of tag sequence
     * @param name the name of the adding tag
     * @param qName qualifier name of the adding tag
     * @param attributes attributes of the adding tag
     * @see #addAfterTag(String, String, Attributes, String)
     * @see #addBeforeTag(String, String, Attributes, String)
     */
    public void addTag(String name, String qName, Attributes attributes){
        OpenTag tag = new OpenTag(name, qName, attributes);
        if (root == null){
            root = new Entry(null, tag);
        } else {
            root = root.addTag(tag);
        }
    }

    /**
     * Add specific tag before another tag that already exist in tag sequence
     * @param name the name of the adding tag
     * @param qName qualifier name of the adding tag
     * @param attributes attributes of the adding tag
     * @param parrentQName qualifier name of the tag before which you want to
     * add current tag
     * @throws IllegalArgumentException if tag before which you want to add
     * current does not exist in tag sequence
     * @see #addAfterTag(String, String, Attributes, String)
     * @see #addTag(String, String, Attributes)
     */
    public void addBeforeTag(String name, String qName, Attributes attributes,
            String parrentQName) throws IllegalArgumentException {
        throw new NotImplementedException();
    }

    /**
     * Add specific tag after another tag that already exist in tag sequence
     * @param name the name of the adding tag
     * @param qName qualifier name of the adding tag
     * @param attributes attributes of the adding tag
     * @param parrentQName qualifier name of the tag after which you want to
     * add current tag
     * @throws IllegalArgumentException if tag after which you want to add
     * current does not exist in tag sequence
     * @see #addBeforeTag(String, String, Attributes, String)
     * @see #addTag(String, String, Attributes) 
     */
    public void addAfterTag(String name, String qName, Attributes attributes,
            String parrentQName) throws IllegalArgumentException {
        throw new NotImplementedException();
    }

    /**
     * Close the tag, if there was some not closed subtags they also will be
     * closed
     * @param qName the name of the closing tag
     * @throws IllegalArgumentException if no opening tag for current close tag
     */
    public void closeTag(String qName) throws IllegalArgumentException {
        while (!root.tag.qName.equals(qName)) {
            root = root.parrent;
        }

        if (root.parrent == null){
            root.notModifi = true;
        } else {
            root = root.parrent;
        }

        onClose(qName);
    }

    /**
     * For example you want to close tag A after tag B, for this you need to 
     * do the next call of this method:<pre>
     * closeTagAfterAnother("B", "A");
     * </pre> You can close unlimited amount of tag after another tag, for
     * example:<pre>
     * closeTagAfterAnother("B", "A2");
     * closeTagAfterAnother("B", "A3");
     * </pre>
     * @param qName qualifier name of the tag after what you want to close the
     * another tag
     * @param anotherTagName the name of the closing tag
     */
    public void closeTagAfterAnother(String qName, String anotherTagName){
        List<String> set = forColsing.get(qName);

        if (set == null){
            set = new LinkedList<String>();
            forColsing.put(qName, set);
        }

        set.add(anotherTagName);
    }

    /**
     * Add text tag to the stream of tags.
     * @param text the text that will be added
     */
    public void addText(String text){
        root.addText(text);
    }

    private void onClose(String qName) {
        List<String> get = forColsing.remove(qName);
        if (get != null){
            Collections.reverse(get);
            for (String name : get) {
                closeTag(name);
            }
        }

        //
    }

    @Override
    public String toString() {
        Entry tmp = root;
        while(root.parrent != null){
            root = root.parrent;
        }
        
        String rootString = root.toString();
        root = tmp;

        return rootString;
    }

    private class Entry {
        final OpenTag tag;
        final String text;
        final List<Entry> childrens;
        final Entry parrent;
        boolean notModifi = false;

        private Entry(Entry parrent, OpenTag tag, String text) {
            this.tag = tag;
            this.text = text;
            this.parrent = parrent;

            if (text == null){
                childrens = new ArrayList<Entry>();
            } else {
                childrens = null;
            }
        }

        public Entry(Entry parrent, OpenTag tag) {
            this(parrent, tag, null);
        }

        public Entry(Entry parrent, String text) {
            this(parrent, null, text);
        }
        
        public boolean isTag(){
            return tag != null;
        }

        public Entry addTag(OpenTag tag){
            if(notModifi){
                throw new IllegalArgumentException("Can't add tag " + tag.qName
                        + ", probebly you close root element of xml");
            }

            Entry entry = new Entry(this, tag);
            childrens.add(entry);
            return entry;
        }

        public void addText(String text){
            if(notModifi){
                return;
            }

            Entry entry = new Entry(this, text);
            childrens.add(entry);
        }

        @Override
        public String toString() {
            if (isTag()){
                StringBuilder sb = new StringBuilder();
                sb.append(tag.getText());
                for (Entry entry : childrens) {
                    sb.append(entry.toString());
                }
                sb.append(tag.getClose());
                return sb.toString();
            } else {
                return String.valueOf(text);
            }
        }

    }
}
